﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Xml.Serialization;
using BSDS.Core.Infrastructure.Configuration;
using BSDS.Core.Services.BingRestApi.Schema;

namespace BSDS.Core.Services.BingRestApi
{
    /// <summary>
    /// The Bing Maps REST Api implementation of <see cref="ILocationService"/>
    /// </summary>
    public class BingRestLocationService: ILocationService
    {
        private const string BingRestUrl = "http://dev.virtualearth.net/REST/v1/";

        private readonly BingKeyLazyLoader bingKey;
        private readonly XmlSerializer xmlSerializer;
        private readonly Func<string, string> urlEncoder;

        public BingRestLocationService(BingKeyLazyLoader bingKey)
        {
            this.bingKey = bingKey;

            xmlSerializer = new XmlSerializer(typeof(Response));

            //TODO: disasemble HttpUtility
            urlEncoder = s => s;
        }


        public IEnumerable<Location> FindLocationByAddress(string countryRegion, string adminDistrict, string locality, string addressLine, string postalCode)
        {
            var url = new StringBuilder(BingRestUrl);
            url.Append("Locations");

            url.Append("?o=xml&key=").Append(bingKey.Value);

            if (countryRegion != null)
            {
                url.Append("&countryRegion=").Append(urlEncoder(countryRegion));
            }

            if (adminDistrict != null)
            {
                url.Append("&adminDistrict=").Append(urlEncoder(adminDistrict));
            }

            if (locality != null)
            {
                url.Append("&locality=").Append(urlEncoder(locality));
            }

            if (addressLine != null)
            {
                url.Append("&addressLine=").Append(urlEncoder(addressLine));
            }

            if (postalCode != null)
            {
                url.Append("&postalCode=").Append(urlEncoder(postalCode));
            }

            using (var response = GetResponse(url.ToString()))
            {
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    throw new InvalidOperationException(
                        string.Format(
                            "The bing location service respond with code {0} and description '{1}' for the query '{2}'.",
                            response.StatusCode, response.StatusDescription, url));
                }

                if (!response.ContentType.StartsWith("application/xml", StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new InvalidOperationException(
                        string.Format(
                            "The bing location service returned response in unrecognized format '{0}'.",
                            response.ContentType));
                }

                Encoding encoding;

                try
                {
                    encoding = Encoding.GetEncoding(response.CharacterSet);
                }
                catch
                {
                    throw new InvalidOperationException(string.Format(
                            "The bing location service returned response in unrecognized encoding '{0}'.",
                            response.ContentEncoding));
                }

                using (var streamReader = new StreamReader(response.GetResponseStream(), encoding))
                {
                    var bingResponse = (Response) xmlSerializer.Deserialize(streamReader);
                    return ProcessResponse(bingResponse);
                }
            }
        }

        protected virtual HttpWebResponse GetResponse(string url)
        {
            var request = (HttpWebRequest)WebRequest.Create(url);
            var response = (HttpWebResponse)request.GetResponse();
            return response;
        }

        private static IEnumerable<Location> ProcessResponse(Response response)
        {
            if (response.StatusCode != 200)
            {
                throw new InvalidOperationException(string.Format(
                        "The bing location service returned error code {0} with error description '{1}' and error details '{2}'.",
                        response.StatusCode, response.StatusDescription, response.ErrorDetails));
            }

            return response.ResourceSets
                .SelectMany(s => s.Resources)
                .OfType<Location>();
        }
    }
}
